import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  ElementRef,
  Renderer2,
  ChangeDetectorRef,
  Input,
  TemplateRef,
  ViewChild,
  Output,
  EventEmitter,
} from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { InputBoolean, TypeUtils } from '../core/api';
import { PopoverComponent } from '../popover/api';

export enum ColorTypeEnum {
  HEX = 'hex',
  RGB = 'rgb',
  HSB = 'hsb',
}

@Component({
  selector: 'lv-color-picker',
  templateUrl: './color-picker.component.html',
  exportAs: 'ColorPickerComponent',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'lv-color-picker',
  },
})
export class ColorPickerComponent implements OnInit {
  @Input() lvClassName: string = '';

  @Input() lvValue: string = 'ff0000';

  @Input() @InputBoolean() lvDisabled: boolean;

  @Input() lvFormat: string = ColorTypeEnum.HEX;

  @Input() lvHeader: string | TemplateRef<void> = null;

  @Input() lvFooter: string | TemplateRef<void> = null;

  @Input() @InputBoolean() lvShowArrow: boolean = true;

  @Input() lvAnimationType: 'popup' | 'slide' = 'popup';

  @Input() @InputBoolean() lvPasteAllowed: boolean = true;

  @Input() @InputBoolean() lvShowColorText: boolean = true;

  @Output() lvChange: EventEmitter<any> = new EventEmitter();

  @Output() lvValueChange: EventEmitter<any> = new EventEmitter();

  @Input() lvScrollToClose: boolean | { exclusions: HTMLElement[] } = true;

  @ViewChild(PopoverComponent, { static: true }) popover: PopoverComponent;

  public element;

  public originEle;

  public value: any;

  clickListener: () => void;

  mouseupListener: () => void;

  hueMoveListener: () => void;

  public typeUtils = TypeUtils;

  mousemoveListener: () => void;

  public inputColorValue: string;

  public hueDragging: boolean = false;

  public colorDragging: boolean = false;

  public inputChange$ = new Subject<any>();

  private hue: any;

  private hueHandle: any;

  private colorHandle: any;

  private colorPalette: any;

  constructor(
    public elementRef: ElementRef,
    private render: Renderer2,
    private cdr: ChangeDetectorRef,
  ) {}

  show = (): void => {
    this.colorPalette = document.querySelector('.lv-color-picker-palette');
    this.colorHandle = document.querySelector('.lv-color-picker-color-handle');
    this.hue = document.querySelector('.lv-color-picker-hue');
    this.hueHandle = document.querySelector('.lv-color-picker-hue-handle');

    this.lvValue = this.lvValue ? this.lvValue : 'ff0000';
    this.setValue(this.lvValue);
  };

  updateModel(): void {
    this.lvValue = this.getValueToFormat();
  }

  HEXtoHSB(hex): { b: number; s: number; h: number } {
    return this.RGBtoHSB(this.HEXtoRGB(hex));
  }

  HSBtoHEX(hsb, isColor = true): string {
    return isColor ? `#${this.RGBtoHEX(this.HSBtoRGB(hsb))}` : this.RGBtoHEX(this.HSBtoRGB(hsb));
  }

  unbindDocumentMouseupListener(): void {
    if (this.mouseupListener) {
      this.mouseupListener();
      this.mouseupListener = null;
    }
  }

  unbindDocumentMousemoveListener(): void {
    if (this.mousemoveListener) {
      this.mousemoveListener();
      this.mousemoveListener = null;
    }
  }

  updateColorPalette(): void {
    const value = { h: this.value.h, s: 100, b: 100 };
    this.colorPalette.style.backgroundColor = this.HSBtoHEX(value);
  }

  inputChange(event: MouseEvent): void {
    this.setValue(this.inputColorValue, 'colorInput');
    this.lvValue = this.getValueToFormat();

    this.inputChange$.next({ value: this.lvValue, e: event });
  }

  validateRGB(rgb): { r: number; b: number; g: number } {
    return {
      r: Math.min(255, Math.max(0, rgb.r)),
      g: Math.min(255, Math.max(0, rgb.g)),
      b: Math.min(255, Math.max(0, rgb.b)),
    };
  }

  validateHSB(hsb): { b: number; s: number; h: number } {
    return {
      h: Math.min(360, Math.max(0, hsb.h)),
      s: Math.min(100, Math.max(0, hsb.s)),
      b: Math.min(100, Math.max(0, hsb.b)),
    };
  }

  onHueMousedown(event: MouseEvent): void {
    if (this.lvDisabled) {
      return;
    }

    this.bindMousemoveListener();
    this.bindMouseupListener();

    this.hueDragging = true;
    this.pickHue(event);
  }

  onColorMousedown(event: MouseEvent): void {
    if (this.lvDisabled) {
      return;
    }

    this.bindMousemoveListener();
    this.bindMouseupListener();

    this.colorDragging = true;
    this.pickColor(event);
  }

  validateHEX(h): any {
    let hex = h;
    const len = 6 - hex.length;
    if (len > 0) {
      const o = [];
      for (let i = 0; i < len; i++) {
        o.push('0');
      }
      o.push(hex);
      hex = o.join('');
    }
    return hex;
  }

  RGBtoHEX(rgb): string {
    const hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];

    hex.forEach((value, index) => {
      if (value.length === 1) {
        hex[index] = `0${value}`;
      }
    });

    return hex.join('');
  }

  getValueToFormat(): any {
    let formatValue: any;

    const formatMap: { [key: string]: any } = {
      [ColorTypeEnum.HEX]: this.HSBtoHEX(this.value),
      [ColorTypeEnum.RGB]: this.HSBtoRGB(this.value),
      [ColorTypeEnum.HSB]: this.value,
    };

    formatValue = formatMap[this.lvFormat];

    return formatValue;
  }

  HEXtoRGB(hex): { r: number; b: number; g: number } {
    const hexValue = parseInt(hex.indexOf('#') > -1 ? hex.substring(1) : hex, 16);
    return {
      // tslint:disable-next-line: no-bitwise
      r: hexValue >> 16,
      // tslint:disable-next-line: no-bitwise
      g: (hexValue & 0x00ff00) >> 8,
      // tslint:disable-next-line: no-bitwise
      b: hexValue & 0x0000ff,
    };
  }

  bindMouseupListener(): void {
    if (!this.mouseupListener) {
      this.mouseupListener = this.render.listen('document', 'mouseup', (): void => {
        document.body.classList.remove('lv-unselectable-text');
        this.colorDragging = false;
        this.hueDragging = false;
        this.unbindDocumentMousemoveListener();
        this.unbindDocumentMouseupListener();
      });
    }
  }

  updateUI(changeBy?): void {
    this.colorHandle.style.left = `${Math.floor((185 * this.value.s) / 100)}px`;
    this.colorHandle.style.top = `${Math.floor((124 * (100 - this.value.b)) / 100)}px`;
    this.hueHandle.style.left = `${Math.floor(185 - (185 * this.value.h) / 360)}px`;
    if (changeBy !== 'colorInput') {
      this.inputColorValue = this.HSBtoHEX(this.value);
    }

    this.cdr.markForCheck();
  }

  bindMousemoveListener(): void {
    if (!this.mousemoveListener) {
      this.mousemoveListener = this.render.listen('document', 'mousemove', (event: MouseEvent): void => {
        // 禁止选中body文本
        document.body.classList.add('lv-unselectable-text');
        if (this.colorDragging) {
          this.pickColor(event);
        }

        if (this.hueDragging) {
          this.pickHue(event);
        }
      });
    }
  }

  setValue(value: any, changeBy?): void {
    switch (this.lvFormat) {
      case ColorTypeEnum.HEX:
        this.value = this.HEXtoHSB(value);
        break;
      case ColorTypeEnum.RGB:
        this.value = this.RGBtoHSB(value);
        break;
      case ColorTypeEnum.HSB:
        this.value = value;
        break;
      default:
        break;
    }

    if (this.colorPalette) {
      this.updateColorPalette();
    }
    if (this.colorHandle && this.hueHandle) {
      this.updateUI(changeBy);
    }
    this.updateModel();
  }

  ngOnInit(): void {
    // 设置组件触发DOM，和指令配合使用
    this.element = this.element ? this.element : this.elementRef.nativeElement;
    this.popover.element = this.element;
    this.originEle = this.element;

    this.lvClassName = `lv-color-picker-panel ${this.lvClassName}`;

    this.inputChange$.pipe(debounceTime(300)).subscribe((res): void => {
      this.lvValueChange.emit(this.getValueToFormat());
      this.lvChange.emit({
        originalEvent: res.e,
        hex: this.HSBtoHEX(this.value),
        hsb: this.value,
        rgb: this.HSBtoRGB(this.value),
      });
    });
  }

  pickHue(event: MouseEvent): void {
    const left =
      this.hue.getBoundingClientRect().left +
      (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
    this.value = this.validateHSB({
      h: Math.floor((360 * (185 - Math.max(0, Math.min(185, event.pageX - left)))) / 185),
      s: this.value.s,
      b: this.value.b,
    });

    this.updateColorPalette();
    this.updateUI();

    this.updateModel();

    this.lvValueChange.emit(this.getValueToFormat());
    this.lvChange.emit({
      originalEvent: event,
      hex: this.HSBtoHEX(this.value),
      hsb: this.value,
      rgb: this.HSBtoRGB(this.value),
    });
  }

  RGBtoHSB(rgb): { b: number; s: number; h: number } {
    const hsb = {
      h: 0,
      s: 0,
      b: 0,
    };
    const min = Math.min(rgb.r, rgb.g, rgb.b);
    const max = Math.max(rgb.r, rgb.g, rgb.b);
    const delta = max - min;
    hsb.b = max;
    hsb.s = max !== 0 ? (255 * delta) / max : 0;
    if (hsb.s !== 0) {
      if (rgb.r === max) {
        hsb.h = (rgb.g - rgb.b) / delta;
      } else if (rgb.g === max) {
        hsb.h = 2 + (rgb.b - rgb.r) / delta;
      } else {
        hsb.h = 4 + (rgb.r - rgb.g) / delta;
      }
    } else {
      hsb.h = -1;
    }
    hsb.h *= 60;
    if (hsb.h < 0) {
      hsb.h += 360;
    }
    hsb.s *= 100 / 255;
    hsb.b *= 100 / 255;
    return hsb;
  }

  pickColor(event: MouseEvent): void {
    const rect = this.colorPalette.getBoundingClientRect();
    const top = rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
    const left = rect.left + document.body.scrollLeft;
    const saturation = Math.floor((100 * Math.max(0, Math.min(185, event.pageX - left))) / 185); // 饱和度（横轴）
    const brightness = Math.floor((100 * (124 - Math.max(0, Math.min(124, event.pageY - top)))) / 124); // 亮度（纵轴）
    this.value = this.validateHSB({
      h: this.value.h,
      s: saturation,
      b: brightness,
    });

    this.updateUI();
    this.updateModel();

    this.lvValueChange.emit(this.getValueToFormat());
    this.lvChange.emit({
      originalEvent: event,
      hex: this.HSBtoHEX(this.value),
      hsb: this.value,
      rgb: this.HSBtoRGB(this.value),
    });
  }

  HSBtoRGB(hsb): { r: number; b: number; g: number } {
    const { h } = hsb;
    const s = hsb.s / 100;
    const v = hsb.b / 100;

    const h1 = Math.floor(h / 60) % 6;
    const f = h / 60 - h1;
    const p = v * (1 - s);
    const q = v * (1 - f * s);
    const t = v * (1 - (1 - f) * s);
    let r;
    let g;
    let b;

    switch (h1) {
      case 0:
        r = v;
        g = t;
        b = p;
        break;
      case 1:
        r = q;
        g = v;
        b = p;
        break;
      case 2:
        r = p;
        g = v;
        b = t;
        break;
      case 3:
        r = p;
        g = q;
        b = v;
        break;
      case 4:
        r = t;
        g = p;
        b = v;
        break;
      case 5:
        r = v;
        g = p;
        b = q;
        break;
      default:
        break;
    }

    return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
  }
}
